<?php

/*********************************************************************************************************************\
 * LAST UPDATE
 * ============
 * March 22, 2007
 *
 *
 * AUTHOR
 * =============
 * Kwaku Otchere
 * ospinto@hotmail.com
 *
 * Thanks to Andrew Hewitt (rudebwoy@hotmail.com) for the idea and suggestion
 *
 * All the credit goes to ColdFusion's brilliant cfdump tag
 * Hope the next version of PHP can implement this or have something similar
 * I love PHP, but var_dump BLOWS!!!
 *
 * FOR DOCUMENTATION AND MORE EXAMPLES: VISIT http://dbug.ospinto.com
 *
 *
 * PURPOSE
 * =============
 * Dumps/Displays the contents of a variable in a colored tabular format
 * Based on the idea, javascript and css code of Macromedia's ColdFusion cfdump tag
 * A much better presentation of a variable's contents than PHP's var_dump and print_r functions
 *
 *
 * USAGE
 * =============
 * new dBug ( variable [,forceType] );
 * example:
 * new dBug ( $myVariable );
 *
 *
 * if the optional "forceType" string is given, the variable supplied to the
 * function is forced to have that forceType type.
 * example: new dBug( $myVariable , "array" );
 * will force $myVariable to be treated and dumped as an array type,
 * even though it might originally have been a string type, etc.
 *
 * NOTE!
 * ==============
 * forceType is REQUIRED for dumping an xml string or xml file
 * new dBug ( $strXml, "xml" );
 *
 *********************************************************************************************************************/

class dBug {

	var $xmlDepth = array();

	var $xmlCData;

	var $xmlSData;

	var $xmlDData;

	var $xmlCount = 0;

	var $xmlAttrib;

	var $xmlName;

	var $arrType = array("array", "object", "boolean", "recordset");

	var $bInitialized = false;

	var $bCollapsed = false;

	var $arrHistory = array();

	//constructor
	function dBug($var, $forceType = "", $bCollapsed = false) {

		echo "<!-- \n", var_export($var, TRUE), "\n//-->";

		//include js and css scripts
		if (! defined('BDBUGINIT')) {
			define("BDBUGINIT", TRUE);
			$this->initJSandCSS();
		}

		$arrAccept = array("array", "object", "xml", "recordset"); //array of variable types that can be "forced"
		$this->bCollapsed = $bCollapsed;

		if( in_array($forceType, $arrAccept) ) {

			echo $this->{"varIs" . ucfirst($forceType)}($var);

		} else {

			echo $this->checkType($var);

		}	# if

	} # Function


	//get variable name
	function getVariableName() {

		$arrBacktrace = debug_backtrace();

		//possible 'included' functions
		$arrInclude = array("include", "include_once", "require", "require_once");

		//check for any included/required files. if found, get array of the last included file (they contain the right line numbers)
		for($i = count($arrBacktrace) - 1; $i >= 0; $i --) {

			$arrCurrent = $arrBacktrace[$i];

			if(
				array_key_exists("function", $arrCurrent) &&
				(in_array($arrCurrent["function"], $arrInclude) ||
				(0 != strcasecmp($arrCurrent["function"], "dbug")))
			) { continue; }

			$arrFile = $arrCurrent;

			break;

		}	# for

		if (isset($arrFile) && file_exists($arrFile["file"])) {

			$arrLines = file($arrFile["file"]);
			$code = $arrLines[($arrFile["line"] - 1)];

			//find call to dBug class
			preg_match('/\bnew dBug\s*\(\s*(.+)\s*\);/i', $code, $arrMatches);

			return isset_or($arrMatches[1]);

		}	# if

		return "";

	}	# Function

	//create the main table header
	function makeTableHeader($type, $header, $colspan = 2) {

		if (! $this->bInitialized) {
			$header = $this->getVariableName() . " (" . $header . ")";
			$this->bInitialized = true;
		}

		$str_i = ($this->bCollapsed) ? "style=\"font-style:italic\" " : "";

		return implode("", array(
			"<table cellspacing=2 cellpadding=3 class=\"dBug_" . $type . "\">",
			"<tr>",
				"<td ", $str_i,
				"class=\"dBug_", $type, "Header\" colspan=", $colspan,
				" onClick='dBug_toggleTable(this)'>", $header, "</td>",
			"</tr>"
		) );

	}	# Function

	//create the table row header
	function makeTDHeader($type, $header) {

		$str_d = ($this->bCollapsed) ? " style=\"display:none\"" : "";

		return implode("", array(
			"<tr", $str_d, ">",
			"<td valign=\"top\" onClick='dBug_toggleRow(this)' class=\"dBug_" . $type . "Key\">" . $header . "</td>",
			"<td>"
		));

	}	# Function

	//close table row
	function closeTDRow() {

		return "</td></tr>\n";
	}

	//error
	function error($type) {

		$error = "Error: Variable cannot be a";
		// this just checks if the type starts with a vowel or "x" and displays either "a" or "an"
		if (in_array(substr($type, 0, 1), array("a", "e", "i", "o", "u", "x"))) {
			$error .= "n"; }

		return ($error . " " . $type . " type");

	}	# Function

	//check variable type
	function checkType($var) {

		switch (gettype($var)) {

			case "object":
				return $this->varIsObject($var);
				break;

			case "array":
				return $this->varIsArray($var);
				break;

			case "NULL":
				return $this->varIsNULL();
				break;

			case "boolean":
				return $this->varIsBoolean($var);
				break;

			default:

				return
					"<table cellspacing=0><tr>\n<td style='font-weight: bold'>" .
					(($var == "") ? "[empty string]" : $var) .
					"</td>\n</tr>\n</table>\n";

				break;

		}	# switch

	}	# function

	//if variable is a NULL type
	function varIsNULL() {

		return "NULL";

	}

	//if variable is a boolean type
	function varIsBoolean($var) {

		$var = ($var == 1) ? "TRUE" : "FALSE";
		return $var;

	}

	function varIsRecordset($var) {

		$html_rows  = "";
		$hd         = $this->getVariableName();
		$lkeys      = array();
		$hd_colspan = 0;
		$count      = count($var);
		$c = 0;

		foreach( $var as $_k => $_i ) {

			$keys = array_keys($_i);
			$hd_colspan = ( count($keys) > $hd_colspan ) ? count($keys) + 1 : $hd_colspan;

			if( $lkeys != $keys ) {

				$html_rows .= "<tr><td class='dbug_recordsetKey' align='right'>#</td>";

				foreach( $keys as $_kt ) {
					$html_rows .= '<td class="dbug_recordsetKey">' . $_kt . '</td>'; }

				$html_rows .= "</tr>";

				$lkeys = $keys;

			}	# if

			$html_rows .= "<tr class='" . ((++$c % 2) ? "odd" : "even") . "'><td valign='top' class='dbug_recordsetKey' align='right'>{$_k}</td>";

			foreach( $keys as $_kk ) {

				$align = is_numeric($var[$_k][$_kk]) ? "right" : "left";

				switch( gettype( isset_or($var[$_k][$_kk]) ) ) {

				case "boolean":
					$html_rows .= "<td align='center' valign='top' style='color: " . (isset_or($var[$_k][$_kk]) ? "green" : "red") .  ";'>" . (isset_or($var[$_k][$_kk]) ? "TRUE" : "FALSE") . "</td>";
					break;

				case "array":

					if( count( isset_or($var[$_k][$_kk]) ) ) {

						try {

							$html_rows .= "<td valign='top' align='$align' style='border: 2px solid blue;'>" . $this->varIsRecordset( isset_or($var[$_k][$_kk]) ) . "</td>";

						} catch( exception $e ) {

							$html_rows .= "<td valign='top' align='$align'>" . $this->varIsArray( isset_or($var[$_k][$_kk]) ) . "</td>";

						}

					} else {

						$html_rows .= "<td valign='top' align='center' style='color: gray; font-size: 9pt;'>(empty array)</td>";

					}

					break;

				case "object":
					$html_rows .= "<td valign='top' align='$align'>" . $this->varIsObject( isset_or($var[$_k][$_kk]) ) . "</td>";
					break;

				case "string":
				case "integer":
				case "double":

					if( !is_numeric($var[$_k][$_kk]) ) {

						if( strlen($var[$_k][$_kk]) ) {

							$html_rows .= "<td valign='top' align='$align' title='" . strlen(isset_or($var[$_k][$_kk], "")) . " chars.'>" . isset_or($var[$_k][$_kk]) . "</td>";

						} else {

							$html_rows .= "<td valign='top' align='center' style='color: gray; font-size: 9pt;'>(empty)</td>";

						}

					} else {

						$html_rows .= "<td valign='top' align='$align' style='color: #490049;'>" . isset_or($var[$_k][$_kk]) . "</td>";

					}	# if

					break;

				case "NULL":
					$html_rows .= "<td valign='top' align='center' style='color: gray; font-size: 9pt;'>(NULL)</td>";
					break;

				default:
					$html_rows .= "<td valign='top' align='$align'>" . isset_or($var[$_k][$_kk]) . "</td>";
					break;

				}	# switch

			}	# foreach

			$html_rows .= "</tr>";

		}	# foreach

return <<<HTML
<table cellspacing="2" cellpadding="3" class="dBug_recordset">
<tbody>
<tr>
	<td colspan="{$hd_colspan}" class="dBug_recordsetHeader">{$hd} (array) ({$count} items) </td>
</tr>
{$html_rows}
</tbody>
</table>
HTML;

	}	# function

	//if variable is an array type
	function varIsArray($var) {

		$r = array();

		/**
		 * @MAF
		 **/
		try {
			$var_ser = serialize($var);
		} catch ( exception $e ) {
			$var_ser = spl_object_hash($e);
		}

		array_push($this->arrHistory, $var_ser);

		$r[] = $this->makeTableHeader("array", "array");

		if (is_array($var)) {

			foreach ( $var as $key => $value ) {

				$r[] = $this->makeTDHeader("array", $key);

				//check for recursion
				if (is_array($value)) {

					/**
					 * @MAF
					 **/
					try {
						$var_ser = serialize($value);
					} catch ( exception $e ) {
						$var_ser = spl_object_hash($e);
					}

					if (in_array($var_ser, $this->arrHistory, TRUE)) {
						$value = "*RECURSION*"; }

				}	# if

				if (in_array(gettype($value), $this->arrType)) {

					$r[] = $this->checkType($value);

				} else {

					$value = (trim($value) == "") ? "[empty string]" : $value;
					$r[] = $value;

				} 	# if

				$r[] = $this->closeTDRow();
			}

		} else {

			$r[] = "<tr><td>" . $this->error("array") . $this->closeTDRow();

		}

		array_pop($this->arrHistory);

		$r[] =  "</table>";

		return implode("", $r);

	}	# function

	//if variable is an object type
	function varIsObject($var) {

		$r = array();

		/**
		 * @MAF
		 **/
		try {
			$var_ser = serialize($var);
		} catch ( exception $e ) {
			$var_ser = spl_object_hash($e);
		}

		array_push($this->arrHistory, $var_ser);

		$r[] = $this->makeTableHeader("object", "object");

		if (is_object($var)) {

			$arrObjVars = get_object_vars($var);

			foreach ( $arrObjVars as $key => $value ) {

				$value = (! is_object($value) && ! is_array($value) && trim($value) == "") ? "[empty string]" : $value;

				$r[] = $this->makeTDHeader("object", $key);

				//check for recursion
				if (is_object($value) || is_array($value)) {

					/**
					 * @MAF
					 **/
					try {
						$var_ser = serialize($value);
					} catch ( exception $e ) {
						$var_ser = spl_object_hash($e);
					}

					if (in_array($var_ser, $this->arrHistory, TRUE)) {

						$value = (is_object($value)) ? "*RECURSION* -> $" . get_class($value) : "*RECURSION*";

					}

				}

				if (in_array(gettype($value), $this->arrType)) {

					$r[] = $this->checkType($value);

				} else {

					$r[] =  $value;

				}

				$r[] = $this->closeTDRow();

			}

			$arrObjMethods = get_class_methods(get_class($var));

			foreach ( $arrObjMethods as $key => $value ) {
				$r[] = $this->makeTDHeader("object", $value);
				$r[] = "[function]" . $this->closeTDRow();
			}

		} else {

			$r[] =  "<tr><td>" . $this->error("object") . $this->closeTDRow();

		}

		array_pop($this->arrHistory);

		$r[] = "</table>";

		return implode("", $r);

	}	# Function

	function initJSandCSS() {

		echo <<<SCRIPTS
			<script language="JavaScript">
			/* code modified from ColdFusion's cfdump code */
				function dBug_toggleRow(source) {
					var target = (document.all) ? source.parentElement.cells[1] : source.parentNode.lastChild;
					dBug_toggleTarget(target,dBug_toggleSource(source));
				}

				function dBug_toggleSource(source) {
					if (source.style.fontStyle=='italic') {
						source.style.fontStyle='normal';
						source.title='click to collapse';
						return 'open';
					} else {
						source.style.fontStyle='italic';
						source.title='click to expand';
						return 'closed';
					}
				}

				function dBug_toggleTarget(target,switchToState) {
					target.style.display = (switchToState=='open') ? '' : 'none';
				}

				function dBug_toggleTable(source) {
					var switchToState=dBug_toggleSource(source);
					if(document.all) {
						var table=source.parentElement.parentElement;
						for(var i=1;i<table.rows.length;i++) {
							target=table.rows[i];
							dBug_toggleTarget(target,switchToState);
						}
					}
					else {
						var table=source.parentNode.parentNode;
						for (var i=1;i<table.childNodes.length;i++) {
							target=table.childNodes[i];
							if(target.style) {
								dBug_toggleTarget(target,switchToState);
							}
						}
					}
				}
			</script>
			<style type="text/css">

				table.dBug_array,
				table.dBug_object,
				table.dBug_resource,
				table.dBug_resourceC,
				table.dBug_recordset,
				table.dBug_xml {
					font-family: 'Lucida Sans Typewriter', Consolas, Trebuchet MS, Tahoma, Verdana, Arial, Helvetica, sans-serif;
					color:#000000;
					font-size: 9pt;
					border-collapse: separate;
					border-spacing: 1px;
					margin: 0px;
					text-align: left;
					-moz-box-shadow:0 0 3px #000022;
				}

				.dBug_arrayHeader,
				.dBug_objectHeader,
				.dBug_resourceHeader,
				.dBug_resourceCHeader,
				.dBug_xmlHeader
					{ font-weight:bold; color:#FFFFFF; cursor:pointer; }

				.dBug_arrayKey,
				.dBug_objectKey,
				.dBug_xmlKey
					{ cursor:pointer; }

				/* array */
				table.dBug_array { background-color:#884488 !important; }
				table.dBug_array td { background-color:#FFFFFF !important; white-space: pre; padding: 3px; }
				table.dBug_array td.dBug_arrayHeader { background-color:#AA66AA !important; }
				table.dBug_array td.dBug_arrayKey { background-color:#FFDDFF !important; }

				/* object */
				table.dBug_object { background-color:#0000CC !important; }
				table.dBug_object td { background-color:#FFFFFF !important; white-space: pre; padding: 2px; }
				table.dBug_object td.dBug_objectHeader { background-color:#4444CC !important; }
				table.dBug_object td.dBug_objectKey { background-color:#CCDDFF !important;  }

				table.dBug_recordset { background-color:#CCCCCC; }
				table.dBug_recordset > td { white-space: pre; padding: 2px; padding-left: 3px; padding-right: 3px; }
				table.dBug_recordset .odd > td { background-color:#EAEFF6; }
				table.dBug_recordset .even > td { background-color:#FFFFFF; }
				table.dBug_recordset tr:hover > td { background-color:#D3E1F2; }
				table.dBug_recordset td.dBug_recordsetHeader { background-color: #89B7F2; color: #000000;  }
				table.dBug_recordset td.dBug_recordsetKey { background-color: #F3F2D4; color: #000000; padding: 3px; }
			</style>
SCRIPTS;
	}

}
?>